package com.wang.mvcframework.servlet;

import com.wang.mvcframework.annotations.*;
import com.wang.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by wangq on 2021/7/23.
 */
public class WangDispatcherServlet extends HttpServlet {
    private Properties properties = new Properties(); // 存放文件配置

    private Map<String, Object> iocMap = new HashMap<>();// 存放实例化的对象

    private List<String> classNameOfScanList = new ArrayList<>(); // 缓存扫描到的类的全限定类名

//    private Map<String, Method> handleMappingMap = new HashMap<>(); // 存储Url和method之间的映射关系

    private List<Handler> handlerMappingList = new ArrayList<>();

    private Map<String, List<String>> securityMap = new HashMap<>(); // 存储Url和有访问权限的用户名集合


    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1、加载配置文件 wangmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2、扫描相关的类、扫描注解
        doScan(properties.getProperty("scanPackage"));

        // 3、初始化bean对象（实现ioc容器，基于注解）
        doInstance();

        // 4、实现依赖注入
        doAutowired();

        // 5、构造一个HandlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMapping();

        // 6、处理各个url的访问权限
        handleUrlSecurity();

        // 等待请求进入 处理请求
        System.out.println("WangDispatcherServlet 初始化完成。。。");

    }

    private void handleUrlSecurity() {
        // 只扫描ioc容器中Controller类的权限配置
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Object controllerObject = entry.getValue();
            Class<?> scanControllerClass = controllerObject.getClass();

            if (!scanControllerClass.isAnnotationPresent(WangController.class)) {
                continue;
            }


            Method[] methods = scanControllerClass.getMethods();


            // 获取类上配置的RequestMapping注解value值
            String baseUrl = "";
            if (scanControllerClass.isAnnotationPresent(WangRequestMapping.class)) {
                baseUrl = scanControllerClass.getAnnotation(WangRequestMapping.class).value();
            }


            // 如果controller类上配置的有@Security注解，该类下的所有handle都校验权限
            if (scanControllerClass.isAnnotationPresent(WangSecurity.class)) {
                // 获取该类上配置的权限用户
                String[] securityUsers = scanControllerClass.getAnnotation(WangSecurity.class).values();

                for (Method method : methods) {
                    // 如果方法上没有配置权限用户，则使用类上配置的权限用户，如果方法上配置了权限用户，则替换类上的权限用户
                    WangSecurity methodAnnotation = method.getAnnotation(WangSecurity.class);
                    List<String> securityUserList = Arrays.asList(methodAnnotation == null ? securityUsers : methodAnnotation.values());
                    // 添加用户到权限集合中
                    putSecurityUser(baseUrl, method, securityUserList);

                }
            } else {
                // 如果controller类上没有@Security注解，该类下的配置了@Security注解的handle都校验权限
                for (Method method : methods) {
                    if (method.isAnnotationPresent(WangSecurity.class)) {
                        // 类上没有配置权限用户，使用方法上配置的权限用户
                        WangSecurity methodAnnotation = method.getAnnotation(WangSecurity.class);
                        List<String> securityUserList = Arrays.asList(methodAnnotation.values());
                        // 添加用户到权限集合中
                        putSecurityUser(baseUrl, method, securityUserList);
                    }

                }

            }

        }
    }

    private void putSecurityUser(String baseUrl, Method method, List<String> securityUserList) {
        WangRequestMapping scanClassMethodAnnotation = method.getAnnotation(WangRequestMapping.class);
        // 如果方法上配置了RequestMapping注解 再加入权限集合
        if (scanClassMethodAnnotation != null) {
            String methodUrl = scanClassMethodAnnotation.value();
            String reqUrl = baseUrl + methodUrl;
            securityMap.put(reqUrl, securityUserList);
        }
    }

    private void initHandlerMapping() {
        if (iocMap.isEmpty()) {
            return;
        }

        // 扫描ioc容器中Controller类的所有方法
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Object controllerObject = entry.getValue();
            Class<?> scanControllerClass = controllerObject.getClass();

            if (!scanControllerClass.isAnnotationPresent(WangController.class)) {
                continue;
            }

            // 获取类上配置的RequestMapping注解value值
            String baseUrl = "";
            if (scanControllerClass.isAnnotationPresent(WangRequestMapping.class)) {
                baseUrl = scanControllerClass.getAnnotation(WangRequestMapping.class).value();
            }

            // 获取Controller类的所有方法
            Method[] scanClassMethods = scanControllerClass.getMethods();
            for (Method scanClassMethod : scanClassMethods) {
                // 如果方法没有标识RequestMapping注解 不处理
                if (!scanClassMethod.isAnnotationPresent(WangRequestMapping.class)) {
                    continue;
                }
                WangRequestMapping scanClassMethodAnnotation = scanClassMethod.getAnnotation(WangRequestMapping.class);
                String methodUrl = scanClassMethodAnnotation.value();
                String url = baseUrl + methodUrl;


                // 把method所有信息封装成一个handler
                Handler handler = new Handler(controllerObject, scanClassMethod, Pattern.compile(url));

                // 处理计算方法参数位置信息
                Parameter[] parameters = scanClassMethod.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request或者 response
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                    } else {
                        //  如果是其他类型普通类型 暂时只区分两种类型
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }

                // 建立url和method的映射关系
//                handleMappingMap.put(url, scanClassMethod);
                handlerMappingList.add(handler);

            }

        }
    }

    private void doAutowired() {
        if (iocMap.isEmpty()) {
            return;
        }
        // 遍历ioc中的所有对象，查看对象中的字段，如果有Autowired注解，需要维护依赖注入关系
        try {
            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
                Object object = entry.getValue();
                Field[] declaredFields = object.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (!declaredField.isAnnotationPresent(WangAutowired.class)) {
                        continue;
                    }
                    WangAutowired wangAutowired = declaredField.getAnnotation(WangAutowired.class);
                    String beanName;
                    if (wangAutowired.value().trim().equals("")) {
                        beanName = declaredField.getType().getSimpleName();
                    } else {
                        beanName = wangAutowired.value();
                    }
                    declaredField.setAccessible(true);
                    declaredField.set(object, iocMap.get(beanName));

                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }


    private void doInstance() {
        if (classNameOfScanList.size() == 0) {
            return;
        }
        try {
            for (String className : classNameOfScanList) {
                // 反射创建对象
                Class<?> scanClass = Class.forName(className);
                if (scanClass.isAnnotationPresent(WangController.class)) {
                    // 如果类上的注解是Controller 就用类名首字母小写为key保存到ioc中
                    String simpleName = scanClass.getSimpleName();
                    String lowerFirstSimpleName = lowerFirst(simpleName);
                    // 实例化对象
                    Object scanClassInstance = scanClass.getDeclaredConstructor().newInstance();
                    iocMap.put(lowerFirstSimpleName, scanClassInstance);
                } else if (scanClass.isAnnotationPresent(WangService.class)) {
                    // 如果类上的注解是Service 并且注解上有value值，就用value值为key保存到ioc中，否则就用类名首字母小写保存到ioc中
                    WangService annotation = scanClass.getAnnotation(WangService.class);
                    //获取注解的value值
                    String annotationValue = annotation.value();
                    // 实例化对象
                    Object scanClassInstance = scanClass.getDeclaredConstructor().newInstance();
                    if (!annotationValue.trim().equals("")) {
                        iocMap.put(annotationValue, scanClassInstance);
                    } else {
                        iocMap.put(lowerFirst(scanClass.getSimpleName()), scanClassInstance);
                    }

                    // service层一般都是有接口的 再以接口名为key，把当前对象存入到ioc中，便于后期使用接口注入
                    Class<?>[] scanClassInterfaces = scanClass.getInterfaces();
                    for (Class<?> scanClassInterface : scanClassInterfaces) {
                        iocMap.put(scanClassInterface.getSimpleName(), scanClassInstance);
                    }

                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }


    }

    private String lowerFirst(String className) {
        char[] chars = className.toCharArray();
        if ('A' <= chars[0] && chars[0] >= 'Z') {
            chars[0] += 32;
        }
        return className;
    }


    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File packageFile = new File(scanPackagePath);
        File[] files = packageFile.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNameOfScanList.add(className);
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求 根据URL找到对应的方法
//        String requestUrl = req.getRequestURI();
//        Method method = handleMappingMap.get(requestUrl);

        // 根据url获取当前请求的handler
        Handler handler = getHandler(req);

        if (handler == null) {
            resp.getWriter().write("404");
            return;
        }


        // 参数绑定
        // 获取方法中所有参数的数组
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        // 创建一个新的数组
        Object[] paramValues = new Object[parameterTypes.length];

        Map<String, String[]> parameterMap = req.getParameterMap();

        // 校验请求是否有权限
        boolean hasSecurity = checkHasSecurity(req, parameterMap);

        if (!hasSecurity) {
            resp.getWriter().write("user has no permission");
            return;
        }

        // 遍历req中所有参数
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String paramName = entry.getKey();
            String value = StringUtils.join(entry.getValue(), ",");

            //如果req参数和方法上的参数匹配上了 填充数据
            if (handler.getParamIndexMapping().containsKey(paramName)) {
                Integer paramIndex = handler.getParamIndexMapping().get(paramName);
                paramValues[paramIndex] = value;
            }
        }
        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paramValues[requestIndex] = req;

        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paramValues[responseIndex] = resp;

        // 最终调用handler的method
        try {
            handler.getMethod().invoke(handler.getController(), paramValues);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private boolean checkHasSecurity(HttpServletRequest req, Map<String, String[]> parameterMap) {
        boolean hasSecurity = false;

        List<String> securityUserList = securityMap.get(req.getRequestURI());
        if (securityUserList == null) {
            return true;
        }

        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String paramName = entry.getKey();
            String value = StringUtils.join(entry.getValue(), ",");
            if (StringUtils.equals(paramName, "name")) {
                if (securityUserList.contains(value)) {
                    hasSecurity = true;
                }
            }
        }
        return hasSecurity;
    }

    /**
     * 通过请求url获取对应的handler
     *
     * @param req 请求信息
     * @return handler
     */
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMappingList.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        for (Handler handler : handlerMappingList) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }
}
